#include "App/udp_recipe_fetch.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include "esp_netif.h"
#include <sys/socket.h>
#include <netdb.h>    
#include "App/sn_handle.h"
#include "tools.h"
#include "App/uart_data.h"
#include "Drivers/wifi_drv.h"

#define TAG "udp_recipe_fetch"

#define SVR_DOMAIN      "tc.yilaiyun.com"
#define SVR_PORT        "61000"             //解析域名使用的端口使用字符串格式
#define DEFAULT_IP      "124.71.111.183"
#define DEFAULT_PORT    61000               //连接服务器端口使用整型格式
#define IP_MAX_LEN      INET_ADDRSTRLEN
#define TC_REQUEST_LEN  SN_LEN + 4          //获取ds请求包的长度，ds + sn + crc
#define RECV_BUF_LEN    256               //接收数据长度
#define SEND_BUF_LEN    256               //发送数据长度

static char g_tc_ip[IP_MAX_LEN];            //tc的IP地址
static char g_ds_ip[IP_MAX_LEN];            //ds的IP地址
static uint16_t g_ds_port = 0;              //ds的端口

static running_err_t getDsFromTc(char *value);

/**
 * @brief 将域名转换为IP地址
 *
 * 该函数通过DNS解析将域名(SVR_DOMAIN)转换为对应的IP地址。
 * 如果DNS解析成功，将获取到的IPv4地址存储在全局变量g_tc_ip中。
 * 如果DNS解析失败，则使用默认IP地址(DEFAULT_IP)。
 *
 * @note 函数使用getaddrinfo进行DNS解析，仅支持IPv4地址
 * @note 解析完成后会自动释放addrinfo资源
 */
static void DomainToIp(void)
{
    struct addrinfo *result;
    if(getaddrinfo(SVR_DOMAIN, SVR_PORT, NULL, &result) != 0)
    {
        ESP_LOGE(TAG, "getaddrinfo failed");
        strcpy(g_tc_ip,DEFAULT_IP);         //失败使用固定IP
    }
    else
    {
        ESP_LOGI(TAG, "getaddrinfo success");
        
        struct sockaddr_in *ipv4 = NULL;

        if (result->ai_family == AF_INET)
        {
            ipv4 = (struct sockaddr_in *)result->ai_addr;
            inet_ntop(result->ai_family, &ipv4->sin_addr, g_tc_ip, sizeof(g_tc_ip));
        }
        
    }
    // ESP_LOGI(TAG, "IP:%s", g_tc_ip);
    freeaddrinfo(result);
}

/**
 * @brief 连接到TC服务器并获取DS的IP地址和端口
 *
 * 该函数通过UDP协议连接到TC服务器，发送包含设备SN的请求，
 * 接收服务器返回的数据并进行CRC校验。
 *
 * @details
 * - 创建UDP socket并设置5秒超时
 * - 发送包含"DS"+SN的请求数据
 * - 接收服务器响应并验证数据格式
 * - 对接收数据进行CRC校验
 * - 关闭socket连接
 *
 * @return running_err_t
 *         - ERR_TC_: 连接失败、发送失败、接收失败或CRC校验错误
 *         - 其他值: getDsFromTc()的返回值
 */
static running_err_t ConnectToTC_Server(void)
{
    int sock;
    int addr_family = AF_INET;
    int ip_protocol = IPPROTO_IP;

    struct sockaddr_in dest_addr = 
    {
        .sin_addr.s_addr = inet_addr(g_tc_ip),
        .sin_family = AF_INET,
        .sin_port = htons(DEFAULT_PORT)
    };

    sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
    if(sock < 0)
    {
        ESP_LOGE(TAG, "TC Unable to create socket: errno %d", errno);
        return ERR_TC_;
    }
    struct timeval timeout;
    timeout.tv_sec = 5;         //设置超时时间
    timeout.tv_usec = 0;
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);    
    ESP_LOGI(TAG, "Connecting to TC server");

    //==========================发送请求======================================//
    char sendBuf[TC_REQUEST_LEN] = {'D','S'};
    char *my_Sn = (char*)GetSN();
    char recvBuf[RECV_BUF_LEN] = {0};

    if(my_Sn[0] == '\0')
    {
        ESP_LOGE(TAG, "SN is NULL");
        return ERR_TC_;
    }
    memcpy(sendBuf + 2, my_Sn, SN_LEN);

    uint16_t crc = crc16_modbus(sendBuf, TC_REQUEST_LEN - 2);
    uint8_t crc_h = (uint8_t)(crc >> 8);
    uint8_t crc_l = (uint8_t)(crc & 0xff);

    sendBuf[TC_REQUEST_LEN - 2] = crc_l;
    sendBuf[TC_REQUEST_LEN - 1] = crc_h;        //服务器crc没有使用大端，所以高低位要调换位置

    int err = sendto(sock, sendBuf, TC_REQUEST_LEN, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    if (err < 0)
    {
        ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
        return ERR_TC_;
    }

    struct sockaddr_storage source_addr;
    socklen_t socklen = sizeof(source_addr);
    int len = recvfrom(sock, recvBuf, RECV_BUF_LEN - 1, 0, (struct sockaddr *)&source_addr, &socklen);
    if (len < 0)
    {
        ESP_LOGE(TAG, "tc_recv failed: errno %d", errno);
        
        return ERR_TC_;
    }else if(strnstr(recvBuf, "DS", 2) == NULL)
    {
        ESP_LOGE(TAG, "tc recv error");
        ESP_LOGE(TAG,"tc recv buf:%s", recvBuf);
        return ERR_TC_;
    }

    crc_h = recvBuf[len - 2];                   //crc校验，高低位调换
    crc_l = recvBuf[len - 1];
    uint16_t recv_crc = (uint16_t)crc_l << 8 | crc_h;
    crc = crc16_modbus(recvBuf, len - 2);

    if(crc != recv_crc)
    {
        ESP_LOGE(TAG, "tc recv crc error");
        return ERR_TC_;
    }    
    shutdown(sock, 0);
    close(sock);
    // ESP_LOGI(TAG,"tc recv buf:%s", recvBuf);
    return getDsFromTc(recvBuf);
}

/**
 * @brief 从 TC 服务器获取 DS 服务器的 IP 地址和端口
 *
 * @param value 包含 DS 服务器信息的字节数组，其中:
 *              value[2-5]: DS 服务器 IP 地址的四个字节
 *              value[6-7]: DS 服务器端口号的两个字节
 *
 * @return running_err_t
 *         - ERR_NONE_: 获取成功
 *         - ERR_TC_: 获取失败
 *
 * @note 函数将解析的 IP 地址存储在全局变量 g_ds_ip 中
 *       将解析的端口号存储在全局变量 g_ds_port 中
 */
static running_err_t getDsFromTc(char *value)
{
    if (!sprintf(g_ds_ip, "%d.%d.%d.%d", value[2], value[3], value[4], value[5]))
        return ERR_TC_;
    g_ds_port = (uint16_t)value[6] << 8 | value[7];
    // char test[] = {0x43, 0x32, 0x34, 0x31, 0x31, 0x30, 0x30, 0x30, 0x30, 0x31, 0x55};
    // getRecipeFromDS(test, sizeof(test));
    ESP_LOGI(TAG, "already get DS ip:%s", g_ds_ip);
    return ERR_NONE_;
}

/**
 * @brief 从数据服务器获取菜谱信息
 *
 * @param recipeInfor 请求的菜谱id信息
 * @param respBuffer 接收服务器响应的菜谱
 * @param inforlen 请求信息的长度
 * @param respLen 响应数据的长度
 *
 * @return running_err_t
 *         - ERR_NONE_: 成功获取菜谱
 *         - ERR_DS_: 与数据服务器通信错误
 *         - ERR_ID_: 菜谱ID错误
 *         - ERR_OVER_LIFE_: 超过使用寿命
 *
 * @note 该函数通过UDP协议与数据服务器通信
 *       发送格式为: CS + SN + recipeInfor + CRC16
 *       接收格式为: CS/ER/FC + data + CRC16
 */
running_err_t getRecipeFromDS(char *recipeInfor, char *respBuffer, int inforlen, int *respLen)
{
    int sock;
    int addr_family = AF_INET;
    int ip_protocol = IPPROTO_IP;
    char sendBuffer[SEND_BUF_LEN] = {'C','S'};
    char *pSendBuffer = sendBuffer + 2;
    char recvBuf[RECV_BUF_LEN] = {0};

    struct sockaddr_in dest_addr = 
    {
        .sin_addr.s_addr = inet_addr(g_ds_ip),
        .sin_family = AF_INET,
        .sin_port = htons(g_ds_port)
    };

    sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
    if (sock < 0)
    {
        ESP_LOGE(TAG, "DS Unable to create socket: errno %d", errno);
        running.IsConnectServer = false;
        return ERR_DS_;
    }

    struct timeval timeout;
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);
    ESP_LOGI(TAG, "Connecting to DS server");

    memcpy(pSendBuffer, (char*)GetSN(), SN_LEN);
    pSendBuffer += SN_LEN;
    memcpy(pSendBuffer, recipeInfor, inforlen);
    
    int currentLen = 2 + 8 + inforlen;               //cs + sn + inforlen

    uint16_t crc = crc16_modbus(sendBuffer, currentLen);
    uint8_t crc_h = (uint8_t)(crc >> 8);
    uint8_t crc_l = (uint8_t)(crc & 0xff);

    sendBuffer[currentLen++] = crc_l;
    sendBuffer[currentLen] = crc_h;

    // for (size_t i = 0; i < currentLen + 1; i++)
    // {
    //     printf("%02x ", sendBuffer[i]);
    // }
    
    int err = sendto(sock, sendBuffer, currentLen + 1, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    if (err < 0)
    {
        ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
        shutdown(sock, 0);
        close(sock);
        running.IsConnectServer = false;
        return ERR_DS_;
    }

    struct sockaddr_storage source_addr;
    socklen_t socklen = sizeof(source_addr);
    int len = recvfrom(sock, recvBuf, RECV_BUF_LEN - 1, 0, (struct sockaddr *)&source_addr, &socklen);
    if (len < 0)
    {
        ESP_LOGE(TAG, "ds_recv failed: errno %d", errno);
        running.IsConnectServer = false;
        return ERR_DS_;
    }

    crc_h = recvBuf[len - 2];
    crc_l = recvBuf[len - 1];
    uint16_t recv_crc = (uint16_t)crc_l << 8 | crc_h;
    crc = crc16_modbus(recvBuf, len - 2);

    if(crc != recv_crc)
    {
        ESP_LOGE(TAG, "ds recv crc error");
        
        return ERR_DS_;
    }
    else if(strnstr(recvBuf, "CS", 2))
    {
        ESP_LOGI(TAG, "Recipe success!");
        running.IsConnectServer = true;
        memcpy(respBuffer, recvBuf + 2, len - 2);
        *respLen = len - 4;
    }
    else if(strnstr(recvBuf, "ER", 2))
    {
        int er = recvBuf[2];
        switch (er)
        {
        case 0x01:
            ESP_LOGE(TAG, "check error");       //1、2开发者看的log
            break;
        case 0x02:
            ESP_LOGE(TAG, "sn error");
            break;
        case 0x03:
            ESP_LOGE(TAG, "id error");          //3、4需要响应到主板
            return ERR_ID_;
        case 0x04:
            ESP_LOGE(TAG, "over life error");
            return ERR_OVER_LIFE_;        
        default:
            break;
        }
    }
    else if(strnstr(recvBuf, "FC", 2))
    {

    }

    // ESP_LOGI(TAG,"ds recv buf:%s,%x", recvBuf, recvBuf[2]);

    shutdown(sock, 0);
    close(sock);

    return ERR_NONE_;
}

/**
 * @brief 食谱获取任务函数
 *
 * 该任务负责从服务器获取食谱数据。执行以下步骤：
 * 1. 将域名解析为IP地址
 * 2. 连接到服务器
 * 3. 处理服务器连接错误,最多重试3次
 *
 * @param param 任务参数(未使用)
 * @note 任务成功连接服务器后会自动退出
 */
void recipeFetchTask(void *param)
{
    UDP_TaskStatus_t status;
    running_err_t err;
    status = DOMAIN_TO_IP;
    uint8_t errCount = 0;

    while (1)
    {
        ESP_LOGI(TAG, "status:%d", status);
        switch (status)
        {
            case DOMAIN_TO_IP:

                DomainToIp();
                status = CONNECT_TO_TC_SERVER;               
                break;
            case CONNECT_TO_TC_SERVER:

                err = ConnectToTC_Server();
                if(err == ERR_NONE_) 
                {
                    running.IsConnectServer = true;
                    goto exit_task;
                }
                else
                    status = SERVER_ERROR;
                break;
            case SERVER_ERROR:
                running.IsConnectServer = false;
                if(err == ERR_TC_ && errCount < 3)
                {
                    errCount++;
                    ResponseWifiStatus(SERVER_DISCONNECTED);
                    vTaskDelay(10000 / portTICK_PERIOD_MS);
                    status = CONNECT_TO_TC_SERVER; 
                }
                else
                    goto exit_task;    
                    
                break;
            default: break;
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
exit_task:
   
    vTaskDelete(NULL);
}